#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
#include "Task.hpp"
#include "Log.hpp"

const int TASK_NUM = 5;

class ThreadPool
{
private:
    std::queue<Task> _task_queue;
    int _task_num;
    bool _stop;

    pthread_mutex_t _mtx;
    pthread_cond_t _cond;
    static ThreadPool* _thread_pool;

private:
    ThreadPool() = default;
    ThreadPool(int num = TASK_NUM) : _task_num(num), _stop(false)
    {
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ThreadPool(const ThreadPool&) = delete;

public:
    static ThreadPool* GetInstance()
    {
        pthread_mutex_t lck = PTHREAD_MUTEX_INITIALIZER;
        if (_thread_pool == nullptr)
        {
            pthread_mutex_lock(&lck);
            if (_thread_pool == nullptr)
            {
                _thread_pool = new ThreadPool(TASK_NUM);
                _thread_pool->InitThreadPool();
            }
            pthread_mutex_unlock(&lck);
        }

        return _thread_pool;
    }

    void InitThreadPool()
    {
        for (int i = 0; i < _task_num; ++i)
        {
            pthread_t tid;
            if (pthread_create(&tid, nullptr, ThreadRoutine, (void*)_thread_pool) != 0)
            {
                LOG(FATAL) << "create pthread failed\n";
                exit(1);
            }
        }
        LOG(INFO) << "thread pool init success\n";
    }

    bool isEmpty()
    {
        return _task_queue.empty();
    }
    void Lock()
    {
        pthread_mutex_lock(&_mtx);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&_mtx);
    }
    void ThreadWait()
    {
        pthread_cond_wait(&_cond, &_mtx);
    }
    void ThreadWakeup()
    {
        pthread_cond_signal(&_cond);
    }

    static void* ThreadRoutine(void* args)
    {
        ThreadPool* tp = (ThreadPool*)args;
        while (!tp->_stop)
        {
            tp->Lock();

            while (tp->isEmpty())
                tp->ThreadWait();

            Task task;
            tp->PopTask(&task);

            tp->Unlock();
            task.ProcessTask();
        }

    }

    void PushTask(const Task& task)
    {
        Lock();

        _task_queue.push(task);

        Unlock();
        ThreadWakeup();
    }

    void PopTask(Task* task)
    {
        *task = _task_queue.front();
        _task_queue.pop();
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_cond);
    }

};
ThreadPool* ThreadPool::_thread_pool = nullptr;